模块:Routemap
local i18n = { errors = { "parameter-missing" = "参数缺失！", "collapsible-block-not-closed" = "折叠单元未闭合！", "collapsible-block-not-open" = "找不到折叠单元开始标记！", "collapsible-block-empty" = "折叠单元不能为空！", "collapsible-block-no-first-row" = "折叠单元没有第一行！", "collapsible-block-no-replacement" = "折单元没有替代！", "colspan-less-rows-than-set" = "行数少于列跨距！", }, "error-categories" = { default = 'Category:包含错误线路图的条目' }, html = { "cell-icon-fmt" = '\ | ', "cell-overlapicon-fmt" = ' ', "cell-icon-fmt-with-overlap" = '\ | %s ', "cell-filler-fmt" = '\n|style="width:8px"| ||style="width:4px; background-color:%s"| ||style="width:8px"|', "cell-filler-empty-fmt" = '\n|style="width:%s"|', "row-linfo4-fmt" = '\ |style="padding-right:3px;text-align:left;%s"|%s',-- parameters:linfo4-width, linfo4 "row-linfo3-fmt" = '%s ', "row-rinfo3-fmt" = ' %s', "row-rinfo4-fmt" = '\ |style="padding-left:3px;text-align:right;%s"|%s',-- parameters:rinfo4-width, rinfo4 "row-general-fmt" = '\ |-%s\ |colspan="%s" style="text-align:right;%s"|%s\ |style="font-family:Liberation Mono,Courier New,Courier,Microsoft JhengHei,Microsoft YaHei,monospace;text-align:left;padding:0 %s;%s"|%s\ |style="padding:0;background-color:%s"| \ \ |style="font-family:Liberation Mono,Courier New,Courier,Microsoft JhengHei,Microsoft YaHei,monospace;text-align:right;padding:0 %s;%s"|%s\ |colspan="%s" style="text-align:left;%s"|%s%s',-- parameters: linfo4-fmt, colspan-left, linfo3+2-width, linfo3+2, linfo1-pad, linfo1-width, linfo1, bg, cells, rinfo1-pad, rinfo1-width, rinfo1, colspan-right, rinfo2+3-width, rinfo2+3, rinfo4-fmt "row-collapsible-begin-fmt" = '\ |-\ |colspan="7" style="padding:0 !important;background-color:%s"|\ ', "row-collapsible-left-button-width" = '45px',-- 50px is the minimal width for показать / скрыть button. Use 40px for show / hide "row-collapsible-left-button-fmt" = '\n! style="padding-right:3px;min-width:%s;%s" |',--parameters: left-button-width, linfo4-width "row-collapsible-left-linfo4+3+2-fmt" = '\ ',-- parameters: linfo4, linfo3+2 "row-collapsible-right-button-width" = '45px',-- 72px is the minimal width for развернуть / свернуть button at 90%. Use 58px for expand / collapse "row-collapsible-right-rinfo2+3+4-fmt" = '\ ',-- parameters: rinfo2+3, linfo4 "row-collapsible-right-button-fmt" = '\n| style="padding-left:3px;font-size:90%%;min-width:%s;%s" |',--parameters: right-button-width, rinfo4-width "row-collapsible-replace-begin-fmt" = '\ |-\ |colspan="7" style="padding:0 %s"| \ ', "colspan-fmt" = '%s\n|-\n| colspan="7" style="background-color:%s;text-align:%s;%s"|\n%s', "empty-row-fmt" = '\n|-\n| style="padding-right:3px;%s" |\n| style="%s" |\n| style="padding:0 %s;%s" |\n|\n| style="padding:0 %s;%s" |\n| style="%s" |\n| style="padding-left:3px;%s" |' } } local p,q={},{} local function formaterror(key,param) local result = mw.ustring.format(i18n.html'colspan-fmt', , '', '', '', '' .. mw.ustring.format(i18n.errorskey or (tostring(key) .. ' %s'), tostring(param or '')) .. '') if mw.site.namespacesmw.title.getCurrentTitle().namespace.isContent then result = result .. (i18n'errors-categories'key or i18n'errors-categories'.default or '') end return result end local function RGBbyCode(code)-- RGB codes for BSicon sets at Commons:Category:Icons for railway descriptions/other colors local colors = {-- Any changes should be discussed at Commons:Talk:BSicon/Colors bahn = 'BE2D2C', ex = 'D77F7E', u = '003399', uex = '6281C0', f = '008000', fex = '64B164', g = '2CA05A', gex = '7EC49A', azure = '3399FF', ex_azure = '99CCFF', black = '000000', ex_black = '646464', blue = '0078BE', ex_blue = '64ACD6', brown = '8D5B2D', ex_brown = 'B89A7F', cerulean = '1A8BB9', ex_cerulean = '73B7D3', cyan = '40E0D0', ex_cyan = '8AEAE1', denim = '00619F', ex_denim = '649EC3', fuchsia = 'B5198D', ex_fuchsia = 'D173B8', golden = 'D7C447', ex_golden = 'E5DA8E', green = '2DBE2C', ex_green = '7FD67E', grey = '999999', ex_grey = 'C0C0C0', jade = '53B147', ex_jade = '95CE8E', lavender = '9999FF', ex_lavender = 'C0C0FF', lime = '99CC00', ex_lime = 'D1E681', ochre = 'CC6600', ex_ochre = 'DEA164', orange = 'FF6600', ex_orange = 'FF9955', purple = '8171AC', ex_purple = 'B1A8CB', red = 'EF161E', ex_red = 'F37176', ruby = 'CC0066', ex_ruby = 'DE64A1', saffron = 'FFAB2E', ex_saffron = 'FFC969', sky = '069DD3', ex_sky = '67C2E3', steel = 'A1B3D4', ex_steel = 'C4CFE3', teal = '339999', ex_teal = '82C0C0', violet = '800080', ex_violet = 'B164B1', yellow = 'FFD702', ex_yellow = 'FFEB81', } return colorscode or colors.bahn end local function cell(icon,overlapIcons) --Icon handling. Each icon is defined as in the following example: --icon ID!~overlap icon ID!@image link target --No limit on overlap icons, just separate them by "!~". local tmp,link={}, if #overlapIcons>0 then tmp = mw.text.split(overlapIcons#overlapIcons, '!@') overlapIcons#overlapIcons = tmp1 if #tmp > 1 then link = tmp2 end tmp = {} for i,v in ipairs(overlapIcons) do if i #overlapIcons then local link=link else local link='' end table.insert(tmp,mw.ustring.format(i18n.html'cell-overlapicon-fmt',mw.text.trim(v),link))end return mw.ustring.format(i18n.html'cell-icon-fmt-with-overlap',mw.text.trim(table.concat(tmp)),icon) end tmp = mw.text.split(icon, '!@') icon = mw.text.trim(tmp1) if #tmp > 1 then link = tmp2 end if icon ~= '' then return mw.ustring.format(i18n.html'cell-icon-fmt', icon, link) else return mw.ustring.format(i18n.html'cell-filler-empty-fmt', '20px') end end local function fillercell(code) if code '' then return mw.ustring.format(i18n.html'cell-filler-empty-fmt', '20px') elseif code 'd' then return mw.ustring.format(i18n.html'cell-filler-empty-fmt', '10px') elseif string.sub(code,1,1) '#' then return mw.ustring.format(i18n.html'cell-filler-fmt', code) else return mw.ustring.format(i18n.html'cell-filler-fmt','#' .. RGBbyCode(code)) end end local function properties(str) --str is a combination of properties with following syntax: --name=value[!@property name1=value1[!@property name1=value1]] and so on local result = {} for i, v in ipairs(mw.text.split(str, '!@')) do if v ~= '' then local t = mw.text.split(v, '=') table.insert(result, t1) result[t1] = table.concat(t, '=', 2) or ''--fill table with pairs "property"="value" end end return result end local function row(pattern,noformatting,filler) --Row handling. Each row looks like the following: --row properties~~linfo4~~linfo3~~linfo2~~linfo1! !(icon pattern)~~rinfo1~~rinfo2~~rinfo3~~rinfo4~~row properties local result = {'linfo4' = , 'linfo3+2' = '', 'linfo1' = '', 'cells' = {}, 'rinfo1' = '', 'rinfo2+3' = '', 'rinfo4' = '', 'rowProp' = {}} local lcolspan, rcolspan, linfo4_fmt, rinfo4_fmt, l1pad, r1pad = '2', '2', '', '', q.linfo1_pad, q.rinfo1_pad local left, rigth, icons, overlapIcons, tmp = {}, {}, {}, {}, mw.text.split(pattern, '! !') if #tmp > 1 then--splitting the pattern by '! !' left = tmp1 ; right = tmp2 else left = '' ; right = tmp1 or '' end tmp = mw.text.split(left, '~~')--analysing the left part if #tmp > 1 then--if there are several ~~ result'linfo1' = mw.getCurrentFrame():preprocess(mw.text.trim(tmp#tmp)) result'linfo3+2' = mw.text.trim(tmp- 1) if #tmp > 2 then tmp- 2 = mw.text.trim(tmp- 2) if tmp- 2 ~= '' then result'linfo3+2' = mw.ustring.format(i18n.html'row-linfo3-fmt', tmp- 2) .. result'linfo3+2' end if #tmp > 3 then tmp- 3 = mw.text.trim(tmp- 3) if tmp- 3 ~= '' then result'linfo4' = mw.getCurrentFrame():preprocess(tmp- 3) lcolspan = '1' linfo4_fmt = mw.ustring.format(i18n.html'row-linfo4-fmt', '', result'linfo4') end if #tmp > 4 then result'rowProp' = properties(mw.text.trim(tmp- 4)) end end end else--assume only linfo2 was provided. result'linfo3+2' = mw.text.trim(tmp1) l1pad = '0' end result'linfo3+2' = mw.getCurrentFrame():preprocess(result'linfo3+2')--expand possible templates in info. tmp = mw.text.split(right, '~~')--analysing the right part if #tmp > 2 then result'rinfo1' = mw.getCurrentFrame():preprocess(mw.text.trim(tmp2)) result'rinfo2+3' = mw.text.trim(tmp3) if #tmp > 3 then tmp4 = mw.text.trim(tmp4) if tmp4 ~= '' then result'rinfo2+3' = result'rinfo2+3' .. mw.ustring.format(i18n.html'row-rinfo3-fmt', tmp4) end if #tmp > 4 then tmp5 = mw.text.trim(tmp5) if tmp5 ~= '' then result'rinfo4' = mw.getCurrentFrame():preprocess(tmp5) rcolspan = '1' rinfo4_fmt = mw.ustring.format(i18n.html'row-rinfo4-fmt', '', result'rinfo4') end if #tmp > 5 then result'rowProp' = properties(mw.text.trim(tmp6)) end end end else--assume only rinfo2 was provided. result'rinfo2+3' = mw.text.trim(tmp2 or '') r1pad = '0' end result'rinfo2+3' = mw.getCurrentFrame():preprocess(result'rinfo2+3') icons = mw.text.split(tmp1, '\\')--splitting the string of icons first by "\" if type(filler) 'string' then result'cells'1 = 'style="height:' .. filler .. '"'--row parameter before any cells for i, v in ipairs(icons) do table.insert(result'cells', fillercell(v)) end--no !@ or !~ for filler row else for i, v in ipairs(icons) do tmp = mw.text.split(v, '!~') iconsi = tmp1 table.remove(tmp, 1) table.insert(overlapIcons, tmp) end for i, v in ipairs(icons) do table.insert(result'cells', cell(v, overlapIconsi)) end end result'cells' = table.concat(result'cells') if result'rowProp''bg' nil or result'rowProp''bg' '' then result'rowProp''bg' = 'transparent' end if noformatting then return result else return mw.ustring.format(i18n.html'row-general-fmt', linfo4_fmt, lcolspan, '', result'linfo3+2', l1pad, '', result'linfo1', result'rowProp''bg', result'cells', r1pad, '', result'rinfo1', rcolspan, '', result'rinfo2+3', rinfo4_fmt) end end q = {collapsibles = -1, text_width = {, , '', '', '', ''}, linfo1_pad = '3px', rinfo1_pad = '3px', bg = '#f9f9f9'} q.isKeyword = function(pattern, i, rows, justTest) if string.sub(pattern, 1, 1) ~= '-' then if justTest then return false else return nil end end--not a valid keyword local tmp = mw.text.split(string.sub(pattern, 2), '%-') if type(q[tmp1]) "function" and tmp1 ~= 'isKeyword' then if justTest then return tmp1 else return q[tmp1](tmp, i, rows) end--valid keyword else if justTest then return false else return nil end end end q'startCollapsible' = function(params, i, rows) table.remove(rows, i) local tmp = q.isKeyword(rowsi, i, rows, true) if tmp then if tmp 'endCollapsible' then return formaterror('collapsible-block-empty') else return formaterror('collapsible-block-no-first-row') .. q.isKeyword(rowsi, i, rows) --no valid keywords that can follow "startCollapsible" end end if q.collapsibles -1 then q.collapsibles = 1 else q.collapsibles = q.collapsibles + 1 end--q.collapsibles -1 means there are no collapsibles at all; 0 - all closed; >0 - some not closed local collapsed, replace, props = params2, params3 or '', properties(table.concat(params, '-', 4))--params1 is the keyword name so all indices are shifted by one. if collapsed nil or collapsed '' then collapsed = 'collapsed' end if props'bg' nil or props'bg' '' then props'bg' = 'transparent' ; props'bg-replace' = q.bg else props'bg-replace' = props'bg' end local mode, float, result if q.rinfo1_pad '' then mode = 'collapsible ' ; float = 'float:right;' else mode = 'mw-collapsible mw-' ; float = '' end result = mw.ustring.format(i18n.html"row-collapsible-begin-fmt", props'bg', mode, collapsed, float) tmp = row(rowsi, true, nil) local linfo4_3_2_fmt, rinfo2_3_4_fmt = '', '' if q.rinfo1_pad '' then if tmp'linfo4' ~= '' or tmp'linfo3+2' ~= '' then linfo4_3_2_fmt = mw.ustring.format(i18n.html'row-collapsible-left-linfo4+3+2-fmt', tmp'linfo4', tmp'linfo3+2') end result = result .. mw.ustring.format(i18n.html'row-general-fmt', mw.ustring.format(i18n.html'row-collapsible-left-button-fmt', i18n.html'row-collapsible-left-button-width', q.text_width1), '1', q.text_width2, linfo4_3_2_fmt, q.linfo1_pad, q.text_width3, tmp'linfo1', tmp'rowProp''bg', tmp'cells', '', '', '', '1', '', '', mw.ustring.format(i18n.html'row-rinfo4-fmt', '', '')) else if tmp'rinfo4' ~= '' or tmp'rinfo2+3' ~= '' then rinfo2_3_4_fmt = mw.ustring.format(i18n.html'row-collapsible-right-rinfo2+3+4-fmt', tmp'rinfo2+3', tmp'rinfo4') end result = result .. mw.ustring.format(i18n.html'row-general-fmt', mw.ustring.format(i18n.html'row-linfo4-fmt', q.text_width1, tmp'linfo4'), '1', q.text_width2, tmp'linfo3+2', q.linfo1_pad, q.text_width3, tmp'linfo1', tmp'rowProp''bg', tmp'cells', q.rinfo1_pad, q.text_width4, tmp'rinfo1', '1', q.text_width5, rinfo2_3_4_fmt, mw.ustring.format(i18n.html'row-collapsible-right-button-fmt', i18n.html'row-collapsible-right-button-width', q.text_width6)) end if replace ~= '' then if q.isKeyword(rows+ 1, i, rows, true) then return result .. formaterror('collapsible-block-no-replacement') end--a plain row needed for replacement table.remove(rows, i) tmp = row(rowsi, true, nil) local padding, right = i18n.html'row-collapsible-right-button-width' .. ' 0 0', '' if q.rinfo1_pad '' then padding = '0 0 ' .. i18n.html'row-collapsible-left-button-width' ; right = 'right:0px;' end result = result .. mw.ustring.format(i18n.html'row-collapsible-replace-begin-fmt', padding, right, props'bg-replace') linfo4_3_2_fmt = '' ; rinfo2_3_4_fmt = '' if q.rinfo1_pad '' then if tmp'linfo4' ~= '' or tmp'linfo3+2' ~= '' then linfo4_3_2_fmt = mw.ustring.format(i18n.html'row-collapsible-left-linfo4+3+2-fmt', tmp'linfo4', tmp'linfo3+2') end result = result .. mw.ustring.format(i18n.html'row-general-fmt', mw.ustring.format(i18n.html'row-linfo4-fmt', '', ''), '1', q.text_width2, linfo4_3_2_fmt, q.linfo1_pad, q.text_width3, tmp'linfo1', tmp'rowProp''bg', tmp'cells', '', '', '', '1', '', '', mw.ustring.format(i18n.html'row-rinfo4-fmt', '', '')) else if tmp'rinfo4' ~= '' or tmp'rinfo2+3' ~= '' then rinfo2_3_4_fmt = mw.ustring.format(i18n.html'row-collapsible-right-rinfo2+3+4-fmt', tmp'rinfo2+3', tmp'rinfo4') end result = result .. mw.ustring.format(i18n.html'row-general-fmt', mw.ustring.format(i18n.html'row-linfo4-fmt', q.text_width1, tmp'linfo4'), '1', q.text_width2, tmp'linfo3+2', q.linfo1_pad, q.text_width3, tmp'linfo1', tmp'rowProp''bg', tmp'cells', q.rinfo1_pad, q.text_width4, tmp'rinfo1', '1', q.text_width5, rinfo2_3_4_fmt, mw.ustring.format(i18n.html'row-rinfo4-fmt', '', '')) end result = result .. i18n.html'row-collapsible-replace-end-fmt' end return result end q'endCollapsible' = function(params, i, rows) if q.collapsibles > 0 then q.collapsibles = q.collapsibles - 1 return i18n.html'row-collapsible-end-fmt' else return formaterror('collapsible-block-not-open') end end q'colspan' = function(params, i, rows) if params2 'end' then return '' end local tmp, j, nrows, props = {}, 0, tonumber(params2), properties(table.concat(params, '-', 3)) if nrows ~= 0 then table.remove(rows, i) end if nrows nil then nrows = #rows - i + 1 end while j < nrows and i <= #rows do j = j + 1 if rowsi '-colspan-end' then j = nrows else table.insert(tmp, rowsi) end if nrows ~= j or i #rows then table.remove(rows, i) end end if j < nrows then j = formaterror('colspan-less-rows-than-set',j) else j = '' end return mw.ustring.format(i18n.html'colspan-fmt', j, props'bg' or '', props'align' or '', props'style' or '', mw.getCurrentFrame():preprocess(table.concat(tmp, '\n'))) end q'filler' = function(params, i, rows) local tmp, height = table.concat(params, '-', 3), '5px' if #params < 3 or tmp '' then return formaterror('parameter-missing') end--TODO: указать имя нужного параметра. if params2 ~= '' then height = params2 end return row(tmp, nil, height) end function p.RGBbyCode(frame) return RGBbyCode(mw.text.trim(frame.args1 or '')) end function p.route(frame) local rows, tmp = mw.text.trim(frame.args'pattern' or ''), {} if rows '' then return formaterror('parameter-missing') end if mw.text.trim(frame.args'bg' or '') ~= '' then q.bg = frame.args'bg' end tmp = mw.text.split(mw.text.trim(frame.args'text-width' or ''), ',') if #tmp 6 then for i = 1, 6 do if tmpi ~= '' then if tonumber(string.sub(tmpi,-1)) then q.text_widthi = 'width:' .. tmpi .. 'px;' else q.text_widthi = 'width:' .. tmpi .. ';' end end end if tmp4 '' and tmp5 '' and tmp6 '' then q.rinfo1_pad = ''--padding for rinfo1 column = 0, not 3px elseif tmp1 '' and tmp2 '' and tmp3 '' then q.linfo1_pad = '' end--padding for linfo1 column = 0, not 3px elseif #tmp 3 then for i = 1, 3 do if tmpi ~= '' then if tonumber(string.sub(tmpi,-1)) then q.text_width+ 3 = 'width:' .. tmpi .. 'px;' else q.text_width+ 3 = 'width:' .. tmpi .. ';' end end end q.linfo1_pad = '' elseif #tmp 1 and tmp1~= then if tonumber(string.sub(tmp1,-1)) then q.text_width5 = 'width:' .. tmp1 .. 'px;' else q.text_width5 = 'width:' .. tmp1 .. ';' end q.linfo1_pad = '' end tmp = {} rows = mw.text.split(rows, '\n') local i, j = next(rows), next(rows, i)--removing empty lines while j ~= nil do if mw.text.trim(rowsj) '' then table.remove(rows, j) else i = j end j = next(rows, i) end for i, v in ipairs(rows) do local keyword = q.isKeyword(v, i, rows) if type(keyword) ~= "string" then table.insert(tmp, row(v, nil, nil)) else table.insert(tmp, keyword) end end if q.collapsibles > 0 then table.insert(tmp, formaterror('collapsible-block-not-closed') .. q'endCollapsible'()) end if q.collapsibles ~= -1 then if q.rinfo1_pad '' then q.text_width1 = q.text_width1 .. 'min-width:' .. i18n.html'row-collapsible-left-button-width' .. ';' else q.text_width6 = q.text_width6 .. 'min-width:' .. i18n.html'row-collapsible-right-button-width' .. ';' end end -- ↓ empty row to set column widths; ↑ if q.collapsibles ≠ -1 and there are collapsible sections, leftmost or rightmost column should be wide enough to accomodate the button table.insert(tmp, mw.ustring.format(i18n.html'empty-row-fmt', q.text_width1, q.text_width2, q.linfo1_pad, q.text_width3, q.rinfo1_pad, q.text_width4, q.text_width5, q.text_width6)) return table.concat(tmp) end return p --[testing in console: print(p.route({['args'={'text-width'='','pattern'=[=[ STR STR]=]}})) ]]